#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

//主线程唤醒子线程，子线程只有被唤醒的时候，才能执行打印操作
//主线程也需要进行打印(将打印看成是共享资源)

//先lock再wait，先signal再unlock，上锁解锁总是最前最后

/*
    Q：
    为什么要添加这个条件变量呢？我拿到了mutex进入临界区，直接该干啥干啥不就行了？
    A：
    条件变量通常用于线程间的同步和通信，它的作用在于允许线程在满足特定条件之前等待，
    并且在条件满足时被唤醒，从而避免了线程忙等待的情况。

    1、在使用互斥锁的情况下，如果线程在临界区内发现条件不满足，
    它就会一直循环检查条件是否满足，这种忙等待的方式会消耗大量的CPU资源。
    
    2、而引入条件变量后，线程可以在条件不满足时调用pthread_cond_wait进入等待状态，
    此时会释放互斥锁，从而让其他线程可以获得互斥锁并修改条件。
    
    3、当条件满足时，其他线程可以调用pthread_cond_signal或pthread_cond_broadcast
    来通知等待的线程，使其从等待状态被唤醒，重新获得互斥锁并检查条件。

*/

#define ERROR_CHECK(ret, funcName) \
do{ \
    if(ret != 0) \
    { \
        printf("%s : %s \n", funcName, strerror(ret));\
        exit(1); \
    } \
}while(0);

//打包
typedef struct
{
    int num;
    pthread_mutex_t mutex;//互斥锁
    pthread_cond_t cond;//条件变量
}Data;

void *threadFunc(void *arg)
{
    Data *pdata = (Data *)arg;//就是从main中传过来的data

    printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    printf("begin child thread\n");
    printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");

    int ret = pthread_mutex_lock(&pdata->mutex);
    //在表达式中，`->` 的优先级最高，其次是 `*`，最后是 `&`。
    ERROR_CHECK(ret, "pthread_mutex_lock child");
    printf("child thread lock\n");

    //wait调用时会自动释放互斥锁，并使调用线程进入阻塞状态，等待条件变量的信号
    //分为两个部分
    //1、上半部：(在条件变量上)排队，解锁，阻塞
    //2、下半部：(从条件变量上)被唤醒, 上锁，函数返回
    //第二次上锁时也要抢锁，抢不到还要接着等，只是条件已满足，随时启动
    printf("child thread wait\n");
    ret = pthread_cond_wait(&pdata->cond, &pdata->mutex);
    ERROR_CHECK(ret, "pthread_cond_wait child");

    //共享资源
    printf("I'm child thread.I'm waking up\n");

    ret = pthread_mutex_unlock(&pdata->mutex);
    ERROR_CHECK(ret, "pthread_mutex_unlock child");
    printf("child thread unlock\n");

    printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    printf("finish child thread\n");
    printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");

    //子线程主动退出函数
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    Data data;
    data.num = 10;

    //初始化互斥锁
    int ret = pthread_mutex_init(&data.mutex, NULL);
    ERROR_CHECK(ret, "pthread_mutex_init");

    //初始化条件变量
    ret = pthread_cond_init(&data.cond, NULL);
    ERROR_CHECK(ret, "pthread_cond_init");

    //创建并启动子线程(但不一定启动)
    pthread_t thid;
    ret = pthread_create(&thid, NULL, threadFunc, &data);
    ERROR_CHECK(ret, "pthread_create");

    //让主线程先睡眠，将CPU的控制权交给子线程
    //等子线程wait了，再回到这里
    //这里不能join，不然会一直卡在子线程，没人唤醒
    printf("main sleep\n");
    usleep(100);

    //肯定要在子线程创建之后
    ret = pthread_mutex_lock(&data.mutex);
    ERROR_CHECK(ret, "pthread_mutex_lock main");
    printf("main thread lock\n");

    //主线程发唤醒子线程去执行任务，也就是打印
    //但此时，主线程还没有释放锁，所以子线程在等锁
    ret = pthread_cond_signal(&data.cond);
    ERROR_CHECK(ret, "pthread_cond_signal main");
    printf("main thread sending signal\n");

    //共享资源
    printf("I'm main thread.\n");

    ret = pthread_mutex_unlock(&data.mutex);
    ERROR_CHECK(ret, "pthread_mutex_unlock main");

    printf("main thread unlock\n");

    //主线程等待子线程的回收
    ret = pthread_join(thid, NULL);
    ERROR_CHECK(ret, "pthread_join");

    //互斥锁销毁
    ret = pthread_mutex_destroy(&data.mutex);
    ERROR_CHECK(ret, "pthread_mutex_destroy");

    //条件变量销毁
    ret = pthread_cond_destroy(&data.cond);
    ERROR_CHECK(ret, "pthread_cond_destroy");

    return 0;
}

